﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace System.Threading.Actors.Utilities
{
    //
    // The Json.Net classes JObject and JArray are not [Serializable].  Thus our replication logic breaks
    // when we try to serialize them.  This class converts those classes into something that is [Serializable].
    //

    [Serializable]
    [JsonConverter(typeof(WrapperConverter))]
    public class JsonObjectWrapper
    {
        internal Dictionary<string, object> m_values = null;
        internal object[] m_array;

        // JSON deserializer needs empty constructor (??)
        public JsonObjectWrapper() { }

        // Construct a wrapper from a JObject
        public JsonObjectWrapper(JObject jobj)
        {
            if (jobj == null) return;
            m_values = new Dictionary<string, object>();
            foreach (var token in jobj.AsJEnumerable())
            {
                switch (token.Type)
                {
                    case JTokenType.Property:
                        JProperty prop = (JProperty)token;
                        object value = ObjectFromJToken(prop.Value);
                        m_values[prop.Name] = value;
                        break;
                    default:
                        throw new NotSupportedException("JsonObjectWrapper ctor can't handle token type " + token.Type);
                }
            }
        }

        // Construct a wrapper from a JArray
        public JsonObjectWrapper(JArray jarray)
        {
            if (jarray == null) return;
            List<object> list = new List<object>();

            foreach (var token in jarray)
            {
                list.Add(ObjectFromJToken(token));
            }

            m_array = list.ToArray();
        }

        // Grab a value given a key (good for wrapped JObjects)
        public object this[string key]
        {
            get
            {
                if (m_values == null) throw new InvalidOperationException("Attempted key indexing on non-dictionary JObjectWrapper");
                return m_values[key];
            }
        }

        // Grab a value given an index (good for wrapped JArrays)
        public object this[int index]
        {
            get
            {
                Contract.Requires(index >= 0);
                if (m_array == null) throw new InvalidOperationException("Attempted int indexing on non-array JObjectWrapper");
                return m_array[index];
            }
        }

        // Convert a JToken to an object
        private static object ObjectFromJToken(JToken token)
        {
            object value = null;
            switch (token.Type)
            {
                case JTokenType.Integer: value = (long)token; break; // Adopt JSON "all ints are longs" policy.
                case JTokenType.String: value = (string)token; break;
                case JTokenType.Boolean: value = (bool)token; break;
                case JTokenType.Float: value = (double)token; break;
                case JTokenType.Date: value = (DateTime)token; break;
                case JTokenType.TimeSpan: value = (TimeSpan)token; break;
                case JTokenType.Null: value = null; break;
                case JTokenType.Guid: value = (Guid)token; break;
                case JTokenType.Uri: value = (Uri)token; break;
                case JTokenType.Object: value = new JsonObjectWrapper((JObject)token); break;
                case JTokenType.Array: value = new JsonObjectWrapper((JArray)token); break;
                default: throw new NotSupportedException("No support for token type " + token.Type);
            }
            return value;
        }

        // Perform a deep comparison to determine equality of two JsonObjectWrapper objects
        public override bool Equals(object obj)
        {
            JsonObjectWrapper other = obj as JsonObjectWrapper;
            if (other == null) return false;

            if (m_values != null)
            {
                if (other.m_values == null) return false;
                if (m_values.Count != other.m_values.Count) return false;
                foreach (var kvp in m_values)
                {
                    if (!other.m_values.ContainsKey(kvp.Key)) return false;
                    if (kvp.Value is object[])
                    {
                        var ours = kvp.Value as object[];
                        var theirs = other.m_values[kvp.Key] as object[];
                        if (theirs == null) return false;
                        if (ours.Length != theirs.Length) return false;
                        for (int i = 0; i < ours.Length; i++)
                        {
                            if (!ours[i].Equals(theirs[i])) return false;
                        }
                    }
                    else
                    {
                        if (!(kvp.Value.Equals(other.m_values[kvp.Key]))) return false;
                    }
                }
            }
            else if (m_array != null)
            {
                if (other.m_array == null) return false;
                if (m_array.Length != other.m_array.Length) return false;
                for (int i = 0; i < m_array.Length; i++) if (!m_array[i].Equals(other.m_array[i])) return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    // Allows us to emit proper JSON for a JsonWrapperObject
    class WrapperConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            JsonObjectWrapper wrapper = (JsonObjectWrapper)value;
            if (wrapper.m_values != null)
            {
                writer.WriteStartObject();
                foreach (var kvp in wrapper.m_values)
                {
                    writer.WritePropertyName(kvp.Key);
                    serializer.Serialize(writer, kvp.Value);
                }
                writer.WriteEndObject();
            }
            else if (wrapper.m_array != null)
            {
                writer.WriteStartArray();
                for (int i = 0; i < wrapper.m_array.Length; i++) serializer.Serialize(writer, wrapper.m_array[i]);
                writer.WriteEndArray();
            }
        }

        // Essentially unused.
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JsonObjectWrapper wrapper = new JsonObjectWrapper((JObject)null);
            return wrapper;
        }

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(JsonObjectWrapper);
        }

    }


}
